using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Diagnostics;

public class Core
{
	public enum AccessLevel
	{
		None = 0,
		Player = 1,
		GameMaster = 2
	}

	public static int maxKingdom = 6;						// maximum number of kingdoms allowed in game
	public static List<Kingdom> galaxyKingdoms = new List<Kingdom>();
	public static Hashtable colorTable = new Hashtable();

	public static Galaxy mainGalaxy;
	public static StarSystem solarOnShow;
	public static AccessLevel playerLevel = AccessLevel.GameMaster;
	public static string PlayerKingName = "Aragon II";
	public static int MaxDevQueue = 6;					// length of planet development queue buffer
	public static int TotalTurn = 0;

	public static bool planetHandle = false;		// planet clicking event handle
	public static bool guiFocus = false;				// controlling gui pause

	public static bool savingGame;
	public static bool loadingGame;
	public static Type[] m_Types;

	// Visual related static variables
	public static GameObject oMainCam = GameObject.Find("mainCamera");
	public static GameObject oGuiCam = GameObject.Find("guiCamera");
	public static GameObject oAltCam = GameObject.Find("altCamera");

	public static bool shutMain = false;

	private static string m_BaseDirectory;
	private static string m_ExePath;
	private static Assembly m_Assembly;

	public static string IndexPath = Path.Combine("Saves/", "Saves0.idx");
	public static string DataPath = Path.Combine("Saves/", "Saves0.bin");

	// for game saving and loading
	private static Dictionary<string, Type> m_AllTypes = new Dictionary<string, Type>();
	private static Dictionary<ulong, Fleet> m_Fleets = new Dictionary<ulong, Fleet>();
	private static Dictionary<int, StarSystem> m_Stars = new Dictionary<int, StarSystem>();
	private static Dictionary<string, Planet> m_Planets = new Dictionary<string, Planet>();
	private static Dictionary<int, Building> m_Buildings = new Dictionary<int, Building>();


	#region getters and setters
	public Type[] TypeRef
	{
		get { return m_Types; }
		set { m_Types = value; }
	}

	public static Dictionary<string, Type> AllTypes
	{
		get { return m_AllTypes; }
	}

	public static Dictionary<ulong, Fleet> Fleets
	{
		get { return m_Fleets; }
	}

	public static Dictionary<int, StarSystem> Stars
	{
		get { return m_Stars; }
	}

	public static Dictionary<string, Planet> Planets
	{
		get { return m_Planets; }
	}

	public static Dictionary<int, Building> Buildings
	{
		get { return m_Buildings; }
	}
	#endregion

	// game initialization
	public static void Init()
	{
		VisualInit(true);

		// temporary player kingdom
		PlayerKingdom pk = new PlayerKingdom(new Race(Galaxy.RaceType.Human));
		pk.KingdomID = 0;
		pk.InitializeKingdom();
		
		//setup guiCamera
		oGuiCam.GetComponent<GuiCamera>().kd = pk;

		//setup altCamera
		oAltCam.GetComponent<StarCamera>().kd = pk as Kingdom;
		oAltCam.active = false;			// disable altCamera, such that main camera displays correctly

		// to register player kingdom
		galaxyKingdoms.Add(pk as Kingdom);

		// to generate enemy kingdoms
		for (int i = 1; i < Galaxy.NumOfKingdom; i++)
		{
			EnemyKingdom km = new EnemyKingdom();
			km.KingdomID = i;
			km.InitializeKingdom();

			// to register enemy kingdom and run the kingdom
			galaxyKingdoms.Add(km as Kingdom);

			km.OnEndTurn();
		}

		foreach (Kingdom kd in galaxyKingdoms)
			UnityEngine.Debug.Log("Race: " + kd.Race.RaceType.ToString() + ", Home: " + kd.Home.Location.ToString());

		pk.OnEndTurn();

		//Debug.Log(Application.dataPath);
		//Debug.Log(Application.loadedLevelName);

		//foreach (Type ty in m_Assembly.GetTypes())
		//  Debug.Log("Type: " + ty.FullName);
	}

	public static void VisualInit(bool genSolars)
	{
		// initialize race color table
		colorTable.Add(Galaxy.RaceType.Human, new Color(1, 0.5f, 0, 1));						// orange
		colorTable.Add(Galaxy.RaceType.Elf, new Color(0, 0.8f, 0.5f, 1));						// teal
		colorTable.Add(Galaxy.RaceType.Gnome, new Color(0.2f, 0.8f, 1.0f, 1));			// cyan
		colorTable.Add(Galaxy.RaceType.Dwarf, Color.yellow);												// yellow
		colorTable.Add(Galaxy.RaceType.Orc, Color.red);															// red
		colorTable.Add(Galaxy.RaceType.Halfling, new Color(0.8f, 0.2f, 1, 1));			// purple
		colorTable.Add(Galaxy.RaceType.Pixie, new Color(1, 0.4f, 0.6f, 1));					// pink
		colorTable.Add(Galaxy.RaceType.DarkElf, new Color(0.4f, 0.2f, 0.8f, 1));		// light blue

		// initialize galaxy and kingdoms
		mainGalaxy = new Galaxy();

		if (genSolars)
			mainGalaxy.InitializeStars(mainGalaxy.StarSystem);
		
		StatusOut.SendPlanetsOut(mainGalaxy.Solars);
		StatusOut.SendTechNamesOut();

		if (mainGalaxy.Homes == null || mainGalaxy.Homes.Count == 0)
		{
			UnityEngine.Debug.Log("Failed to setup home stars!");
			throw new ArgumentException();
		}

		// to generate visual planets
		List<GameObject> golist = new List<GameObject>();

		try
		{
			golist = VisualMisc.VisualImplStars(mainGalaxy.Solars);

			if (golist == null || golist.Count == 0)
			{
				UnityEngine.Debug.Log("Failed to initialize visual planets!");
				throw new ArgumentException();
			}
		}
		catch
		{
			UnityEngine.Debug.Log("Failed to generate visual objects: star.");
			throw new ArgumentException();
		}
	}

	public static void DisableMainCamera()
	{
		shutMain = true;
		oMainCam.transform.position = new Vector3(60000.0f, 60000.0f, 60000.0f);
		oMainCam.transform.rotation = Quaternion.identity;
	}

	public static void EnableMainCamera()
	{
		oAltCam.active = false;
		shutMain = false;
		oMainCam.GetComponent<InitCamera>().follow = true;							// must be set to follow, or camera will not follow current star
	}

	public static string BaseDirectory
	{
		get
		{
			if (m_BaseDirectory == null)
			{
				try
				{
					m_BaseDirectory = ExePath;

					if (m_BaseDirectory.Length > 0)
						m_BaseDirectory = Path.GetDirectoryName(m_BaseDirectory);
				}
				catch
				{
					m_BaseDirectory = "";
				}
			}

			return m_BaseDirectory;
		}
	}

	public static string ExePath
	{
		get
		{
			if (m_ExePath == null)
				m_ExePath = GAssembly.Location;

			return m_ExePath;
		}
	}

	public static Assembly GAssembly
	{
		get { return m_Assembly; }
		set { m_Assembly = value; }
	}


	// constructor of class Core
	public Core()
	{
		//m_Assembly = Assembly.GetEntryAssembly();
		m_Assembly = Assembly.GetExecutingAssembly();

		//Init();
	}

	// static methods
	public static void Save()
	{
		if (savingGame)
			return;

		savingGame = true;

		StatusOut.SaveLoadOut(true);

		if (!Directory.Exists("Saves"))
			Directory.CreateDirectory("Saves");

		GenericWriter idx = new BinaryFileWriter(IndexPath, false);
		GenericWriter bin = new BinaryFileWriter(DataPath, true);

		// to write kingdom objects
		idx.Write((int)galaxyKingdoms.Count);

		foreach (Kingdom kd in galaxyKingdoms)
		{
			idx.Write((string)kd.GetType().FullName);
			idx.Write((int)kd.Race.RaceType);
			idx.Write((int)kd.KingdomID);
		}

		// to write star models
		Dictionary<int, StarSystem> stars = m_Stars;
		idx.Write((int)stars.Count);

		foreach (StarSystem ss in stars.Values)
		{
			idx.Write((int)ss.StarID);
		}

		// to write planet models
		Dictionary<string, Planet> planets = m_Planets;
		idx.Write((int)planets.Count);

		foreach (Planet pl in planets.Values)
		{
			idx.Write((string)pl.PlanetID);
		}


		// to write fleet details
		long start = 0;
		Dictionary<ulong, Fleet> fleets = m_Fleets;

		idx.Write(fleets.Count);
		foreach (Fleet fl in fleets.Values)
		{
			start = bin.Position;

			idx.Write((string)fl.GetType().FullName);
			idx.Write((ulong)fl.FleetID);
			idx.Write((long)start);

			fl.Serialize(bin);

			idx.Write((int)(bin.Position - start));
		}

		// to write building details
		Dictionary<int, Building> buildings = m_Buildings;
		idx.Write((int)buildings.Count);

		foreach (Building bd in buildings.Values)
		{
			start = bin.Position;

			idx.Write((string)bd.GetType().FullName);
			idx.Write(bd.FacilityID);
			idx.Write(start);

			bd.Serialize(bin);

			idx.Write((int)(bin.Position - start));
		}

		// to write planet details
		idx.Write(planets.Count);
		foreach (Planet planet in planets.Values)
		{
			start = bin.Position;

			idx.Write(planet.PlanetID);
			idx.Write(start);

			planet.Serialize(bin);

			idx.Write((int)(bin.Position - start));
		}

		// to write star details
		idx.Write(stars.Count);
		foreach (StarSystem star in stars.Values)
		{
			start = bin.Position;

			idx.Write(star.StarID);
			idx.Write(start);

			star.Serialize(bin);

			idx.Write((int)(bin.Position - start));
		}

		// to write kingdom details
		idx.Write(galaxyKingdoms.Count);
		foreach (Kingdom kingdom in galaxyKingdoms)
		{
			start = bin.Position;

			idx.Write(kingdom.KingdomID);
			idx.Write(start);

			kingdom.Serialize(bin);

			idx.Write((int)(bin.Position - start));
		}


		idx.Close();
		bin.Close();

		savingGame = false;
	}

	public static void Load()
	{
		if (loadingGame || !Directory.Exists("Saves"))
			return;

		loadingGame = true;

		Stopwatch watch = Stopwatch.StartNew();

		Type[] alltypes = GAssembly.GetTypes();

		//foreach (Type type in alltypes)
		//  UnityEngine.Debug.Log("Type: " + type.FullName.ToString());

		if (File.Exists(IndexPath) && File.Exists(DataPath))
		{
			using ( FileStream idx = new FileStream( IndexPath, FileMode.Open, FileAccess.Read, FileShare.Read ) )
			{
				BinaryReader idxReader = new BinaryReader( idx );

				using (FileStream bin = new FileStream(DataPath, FileMode.Open, FileAccess.Read, FileShare.Read))
				{
					BinaryFileReader reader = new BinaryFileReader(new BinaryReader(bin));


					// to load kingdom objects
					galaxyKingdoms = new List<Kingdom>();

					int count = idxReader.ReadInt32();

					ConstructorInfo ctor;
					string tyfname = "";
					Type ty = null;

					for (int i = 0; i < count; i++)
					{
						tyfname = idxReader.ReadString();
						Race race = new Race((Galaxy.RaceType)idxReader.ReadInt32());

						if (tyfname == "PlayerKingdom")
							ty = typeof(PlayerKingdom);
						else if (tyfname == "EnemyKingdom")
							ty = typeof(EnemyKingdom);

						if (ty != null)
						{
							ctor = ty.GetConstructor(new Type[1] { typeof(Race) });
							object[] ctorArgs = new object[1] { race };
							object o = null;

							try
							{
								o = ctor.Invoke(ctorArgs);
							}
							catch
							{
							}

							if (o == null)
								UnityEngine.Debug.Log("o = null");

							if (o != null && o is Kingdom)
							{
								Kingdom kd = o as Kingdom;
								kd.KingdomID = idxReader.ReadInt32();

								galaxyKingdoms.Add(kd);
							}
							else
							{
								throw new NullReferenceException();
							}
						}
						else
						{
							throw new NullReferenceException();
						}
					}

					// to load star models
					count = idxReader.ReadInt32();
					m_Stars = new Dictionary<int, StarSystem>(count);

					for (int i = 0; i < count; i++)
					{
						StarSystem st = new StarSystem();
						st.StarID = idxReader.ReadInt32();

						m_Stars.Add(st.StarID, st);
					}

					// to load planet models
					count = idxReader.ReadInt32();
					m_Planets = new Dictionary<string, Planet>(count);

					for (int i = 0; i < count; i++)
					{
						Planet pl = new Planet();
						pl.PlanetID = idxReader.ReadString();

						m_Planets.Add(pl.PlanetID, pl);
					}

					// to load fleets
					count = idxReader.ReadInt32();
					ulong flid = 0;
					long pos = 0;
					int length = 0;

					m_Fleets = new Dictionary<ulong, Fleet>(count);

					for (int i = 0; i < count; i++)
					{
						tyfname = idxReader.ReadString();
						flid = idxReader.ReadUInt64();
						pos = idxReader.ReadInt64();
						length = idxReader.ReadInt32();

						for (int j = 0; j < alltypes.Length; j++)
							if (alltypes[j].FullName == tyfname)
							{
								ty = alltypes[j];
								break;
							}

						if (ty != null)
						{
							object o = Activator.CreateInstance(ty);

							if (o != null && o is Fleet)
							{
								Fleet fleet = o as Fleet;
								fleet.FleetID = flid;

								fleet.Deserialize(reader);

								m_Fleets.Add(flid, fleet);
							}
							else
							{
								throw new NullReferenceException();
							}
						}
						else
						{
							throw new NullReferenceException();
						}
					}

					// to load buildings
					count = idxReader.ReadInt32();
					int bid = 0;
					pos = 0;
					length = 0;

					m_Buildings = new Dictionary<int, Building>(count);

					for (int i = 0; i < count; i++)
					{
						tyfname = idxReader.ReadString();
						bid = idxReader.ReadInt32();
						pos = idxReader.ReadInt64();
						length = idxReader.ReadInt32();

						for (int j = 0; j < alltypes.Length; j++)
							if (alltypes[j].FullName == tyfname)
							{
								ty = alltypes[j];
								break;
							}

						if (ty != null)
						{
							object o = Activator.CreateInstance(ty);

							if (o != null && o is Building)
							{
								Building build = o as Building;
								build.FacilityID = bid;

								build.Deserialize(reader);

								m_Buildings.Add(bid, build);
							}
							else
							{
								throw new NullReferenceException();
							}
						}
						else
						{
							throw new NullReferenceException();
						}
					}

					// to load planet details
					count = idxReader.ReadInt32();
					for (int i = 0; i < count; i++)
					{
						Planet planet = Core.FindPlanet(idxReader.ReadString());
						pos = idxReader.ReadInt64();
						length = idxReader.ReadInt32();

						planet.Deserialize(reader);
					}

					// to load starsystem details
					count = idxReader.ReadInt32();

					for (int i = 0; i < count; i++)
					{
						StarSystem star = Core.FindStarSystem(idxReader.ReadInt32());
						pos = idxReader.ReadInt64();
						length = idxReader.ReadInt32();

						star.Deserialize(reader);
					}

					// to load kingdom details
					count = idxReader.ReadInt32();

					for (int i = 0; i < count; i++)
					{
						int kid = idxReader.ReadInt32();
						pos = idxReader.ReadInt64();
						length = idxReader.ReadInt32();

						Kingdom kd = galaxyKingdoms[0];

						foreach (Kingdom kingdom in galaxyKingdoms)
							if (kingdom.KingdomID == kid)
								kd = kingdom;

						kd.Deserialize(reader);
					}


					// dummy codes to avoid warning on unused pos and length
					if (pos == 0 || length == 0)
					{
					}

					reader.Close();
				}

				idxReader.Close();
			}
		}
		else
			return;

		watch.Stop();

		StatusOut.SaveLoadOut(false);

		loadingGame = false;
	}

	public static Kingdom FindKingdom(int kid)
	{
		if (kid < 0)
			return null;

		Kingdom kdm = null;

		foreach (Kingdom kd in Core.galaxyKingdoms)
			if (kd.KingdomID == kid)
				kdm = kd;

		return kdm;
	}

	public static StarSystem FindStarSystem(int starid)
	{
		if (starid > 0)
			return m_Stars[starid];
		else
			return null;
	}

	public static Planet FindPlanet(string plid)
	{
		if (plid.Length > 0)
			return m_Planets[plid];
		else
			return null;
	}

	public static Fleet FindFleet(ulong flid)
	{
		if (flid > 0)
			return m_Fleets[flid];
		else
			return null;
	}

	public static Building FindBuilding(int bid)
	{
		if (bid > 0)
			return m_Buildings[bid];
		else
			return null;
	}

	public static Race FindRace(int racetype)
	{
		if (racetype > 0)
		{
			return new Race((Galaxy.RaceType)racetype);
		}
		else
			return null;
	}
}